﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using Fuse8.DomainFramework.CacheService;
using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Exceptions;
using Fuse8.DomainFramework.Common.Locator;
using Fuse8.DomainFramework.DataAccess.ExpressionSlicers;
using Fuse8.DomainFramework.Domain;

namespace Fuse8.DomainFramework.DataAccess.SqlDatabase
{
    /// <summary>
    /// 
    /// </summary>
    public class DynamicSqlEntityDataRepository<TEntity, TKey> : IEntityDataRepository<TEntity, TKey>
        where TEntity : class, IEntityObject<TKey>, new()
        where TKey : IComparable<TKey>, IEquatable<TKey>
    {
        #region Fields
       
        private ISqlEntityDataMapper<TEntity, TKey> _mapper;

        #endregion

        #region Properties

        /// <summary>
        /// 
        /// </summary>
        public bool SetIdInSaveMethod { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public virtual string AddSpName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public virtual string UpdateSpName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public virtual string RemoveSpName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public virtual string FindOneSpName { get; set; }

        /// <summary>
        /// 
        /// </summary>
        public virtual string FindAllSpName { get; set; }

        private static IList<SqlParameter> BuildSqlParamteres (IEnumerable<SqlParameterInfo> parameterInfos, object parameter)
		{
			return parameterInfos == null ? new List<SqlParameter>() : parameterInfos.Select(p => new SqlParameter(p.Name, p.GetterDelegate(parameter) ?? DBNull.Value)).ToList();
		}

        /// <summary>
        /// 
        /// </summary>
        protected virtual ISqlEntityDataMapper<TEntity, TKey> Mapper
        {
            get
            {
                if (_mapper == null)
                {   
                    if (!DependencyServiceLocator.Current.IsDefaultServiceRegistered<ISqlEntityDataMapper<TEntity, TKey>>())
                    {
                        _mapper = SqlEntityDataRepositoryConfiguration<TEntity, TKey>.CreateMapper();
                        DependencyServiceLocator.Current.SetDefaultService<ISqlEntityDataMapper<TEntity, TKey>>(_mapper);
                    }
                    else
                    {
                        _mapper = DependencyServiceLocator.Current.GetInstance<ISqlEntityDataMapper<TEntity, TKey>>();
                    }
                }

                return _mapper;
            }
            set
            {
                _mapper = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        protected virtual SqlUnitOfWork<TEntity> CurrentUnitOfWork
        {
            get
            {
                var unitOfWork = UnitOfWorkController<TEntity>.GetCurrentUnitOfWork() as SqlUnitOfWork<TEntity>;

                if (unitOfWork == null)
                {
                    throw new UnitOfWorkNotDefinedException();
                }

                return unitOfWork;
            }
        }

        #endregion

        #region IEntityDataRepository<TEntity,TKey> Members

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Add(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

            var unitOfWork = CurrentUnitOfWork;

			if (string.IsNullOrEmpty(AddSpName))
			{
				var query = SqlQueryBuilder.BuildInsertQuery<TEntity>(unitOfWork.Connection);
				entityObject.Id = SqlUtility<TEntity, TKey>.ExecuteNonQuery(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, entityObject), Mapper, System.Data.CommandType.Text, true);
			}
			else
			{
				SqlUtility<TEntity, TKey>.ExecuteNonQueryWithMapping(unitOfWork.Connection, unitOfWork.Transaction, AddSpName, entityObject, Mapper, true);
			}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Save(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");

			var unitOfWork = CurrentUnitOfWork;

            if (string.IsNullOrEmpty(UpdateSpName))
            {
                var fieldTracker = entityObject as IFieldTracker;
                var query = SqlQueryBuilder.BuildUpdateQuery<TEntity>(unitOfWork.Connection, fieldTracker);
                if (query != null)
                {
                    SqlUtility<TEntity, TKey>.ExecuteNonQuery(unitOfWork.Connection, unitOfWork.Transaction, query.Query,
                                                              BuildSqlParamteres(query.Parameters, entityObject), Mapper,
                                                              System.Data.CommandType.Text, false);
                }
            }
            else
            {

                SqlUtility<TEntity, TKey>.ExecuteNonQueryWithMapping(unitOfWork.Connection, unitOfWork.Transaction,
                                                                     UpdateSpName, entityObject, Mapper, false);

            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="entityObject"></param>
        public void Remove(TEntity entityObject)
        {
            Guard.ArgumentNotNull(entityObject, "entityObject");
        	Remove(entityObject.Id);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        public void Remove(TKey id)
        {
            var unitOfWork = CurrentUnitOfWork;
			if (string.IsNullOrEmpty(RemoveSpName))
			{
                Expression<Func<TEntity, bool>> expression = SlicerHelper.AdaptEpression<TEntity>(p => p.Id.Equals(id));
				var query = SqlQueryBuilder.BuildDeleteQuery(unitOfWork.Connection, expression);
				SqlUtility<TEntity, TKey>.ExecuteNonQuery(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text, false);
			}
			else
			{
        		SqlUtility<TEntity, TKey>.ExecuteNonQuery(unitOfWork.Connection, unitOfWork.Transaction, RemoveSpName, new { Id = id }, Mapper);
			}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public TEntity FindOne(TKey id)
        {
            var unitOfWork = CurrentUnitOfWork;

			if (string.IsNullOrEmpty(FindOneSpName))
			{
                Expression<Func<TEntity, bool>> expression = SlicerHelper.AdaptEpression<TEntity>(p => p.Id.Equals(id));
				var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression);
				return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).FirstOrDefault();
			}

            var result = SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, FindOneSpName, new { Id = id }, Mapper);

            return result != null ?
                result.FirstOrDefault() :
                default(TEntity);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual TEntity FindOne(Expression<Func<TEntity, bool>> expression)
        {
			var unitOfWork = CurrentUnitOfWork;
            expression = SlicerHelper.AdaptEpression<TEntity>(expression);
			var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression);
			return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).FirstOrDefault();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public TEntity FindOne(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
			var unitOfWork = CurrentUnitOfWork;
            expression = SlicerHelper.AdaptEpression<TEntity>(expression);
			var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression, selectExpression);
			return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).FirstOrDefault();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression)
    	{
    	    var unitOfWork = CurrentUnitOfWork;
            expression = SlicerHelper.AdaptEpression<TEntity>(expression);
    	    var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression);
    	    return
    	        SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query,
    	                                                BuildSqlParamteres(query.Parameters, expression), Mapper,
    	                                                System.Data.CommandType.Text).AsQueryable();
    	}

        /// <summary>
		/// 
		/// </summary>
		/// <param name="expression"></param>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> expression, Expression<Func<TEntity, object>> selectExpression)
    	{
			var unitOfWork = CurrentUnitOfWork;
            expression = SlicerHelper.AdaptEpression<TEntity>(expression);
			var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression, selectExpression);
			return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).AsQueryable();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IQueryable<TEntity> FindAll()
        {
            var unitOfWork = CurrentUnitOfWork;
            if (string.IsNullOrEmpty(FindAllSpName))
            {
                var expression = SlicerHelper.AdaptEpression<TEntity>(null);
                var query = SqlQueryBuilder.BuildFindQuery<TEntity>(unitOfWork.Connection, expression);
				return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).AsQueryable();
            }
            return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, FindAllSpName, null, Mapper)
                .AsQueryable();
        }

		/// <summary>
		/// 
		/// </summary>
		/// <param name="selectExpression"></param>
		/// <returns></returns>
    	public IQueryable<TEntity> FindAll(Expression<Func<TEntity, object>> selectExpression)
    	{
			var unitOfWork = CurrentUnitOfWork;
            var expression = SlicerHelper.AdaptEpression<TEntity>(null);
			var query = SqlQueryBuilder.BuildFindQuery(unitOfWork.Connection, expression, selectExpression);
			return SqlUtility<TEntity, TKey>.ExecuteReader(unitOfWork.Connection, unitOfWork.Transaction, query.Query, BuildSqlParamteres(query.Parameters, expression), Mapper, System.Data.CommandType.Text).AsQueryable();
    	}

    	/// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public TEntity FindOne(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.ExecuteOne();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        /// <returns></returns>
        public IQueryable<TEntity> Find(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            return command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="command"></param>
        public void Execute(IEntityCommand<TEntity> command)
        {
            Guard.ArgumentNotNull(command, "command");

            command.Execute();
        }

        /// <summary>
        /// 
        /// </summary>
        public DataRepositoryCacheOptions CacheOptions
        {
            get 
            {
                return DataRepositoryCacheOptions.FullCache;
            }
        }

        #endregion


    }
}